package com.peycard.business;

import java.security.Security;
import java.util.Arrays;

import javax.annotation.PostConstruct;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.ejb.Stateless;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

import com.peycard.business.local.TDESBeanLocal;
import com.peycard.common.util.Constantes;

/**
 * Session Bean implementation class TDESBean
 */
@Stateless
public class TDESBean implements TDESBeanLocal {
private static String BOUNCY_CASTLE_PROVIDER = "BC";
	
    /**
     * Default constructor. 
     */
    public TDESBean() {
        // TODO Auto-generated constructor stub
    }
    
    /**
     * Metodo que agrega el proveedor de seguridad de BC
     */
    @PostConstruct
    public void init(){
    	Security.addProvider(new BouncyCastleProvider());
    }
    
    /* (sin Javadoc)
     * @see com.peycard.business.local.TDESBeanLocal#getClearData(java.lang.String, java.lang.String, java.lang.String)
     */
    @Override
    public String getClearData(String encryptedData, String ksn, String bdk) throws BusinessException{
    	try {
			String keyVariant = this.GenerateKeyVariant(ksn, bdk);
			String result = this.TDES_Dec(encryptedData, keyVariant);
			return result;
		} catch (Exception e) {
			throw new BusinessException(Constantes.ERROR_GENERICO_NEGOCIO, e);
		}
    	
    }
    
	/**
	 * Genera la variante de llave a partir del KSN y el BDK
	 * @param strKSN
	 * @param strBDK
	 * @return
	 * @throws Exception
	 */
	private String GenerateKeyVariant(String strKSN, String strBDK)throws Exception {	
		
		byte[] ksn, bdk, ipek;
        byte[] baTemp;
        ksn = new byte[10];
        bdk = new byte[16];
        ksn = this.String2Hex(strKSN);
        bdk = this.String2Hex(strBDK);
        ipek = this.GenerateIPEK(ksn, bdk);
        baTemp = this.GetDataKeyVariant(ksn, ipek);
        return b2h(baTemp);
        }
	
	/**
	 * Genera la variante de llave DUKPT a partir de arreglos de bytes
	 * @param ksn
	 * @param ipek
	 * @return
	 * @throws Exception
	 */
	private byte[] GetDataKeyVariant(byte[] ksn, byte[] ipek) throws Exception {
		byte[] key;
        key = GetDUKPTKey(ksn, ipek);
        key[5] ^= 0xFF;
        key[13] ^= 0xFF;
        return key;
        }
	
	/**
	 * Obtiene la llave DUKPT
	 * @param ksn
	 * @param ipek
	 * @return
	 * @throws Exception
	 */
	private byte[] GetDUKPTKey(byte[] ksn, byte[] ipek) throws Exception {
		byte[] key;
        byte[] cnt;
        byte[] temp;
        byte shift;
        int shiftTemp;
        key = new byte[16];
        System.arraycopy(ipek, 0, key, 0, 16);
        
        temp = new byte[8];
        cnt = new byte[3];
        cnt[0] = (byte)(ksn[7] & 0x1F);
        cnt[1] = ksn[8];
        cnt[2] = ksn[9];
        System.arraycopy(ksn, 2, temp, 0, 6);
        temp[5] &= 0xE0;
        
        shift = 0x10;
        while (shift > 0){
            if ((cnt[0] & shift) > 0){
                temp[5] |= shift;
                NRKGP(key, temp);
            }
            shift >>= 1;
        }
        shiftTemp = 128;
        while (shiftTemp > 0){
            if ((cnt[1] & shiftTemp) > 0){
                temp[6] |= shiftTemp;
                NRKGP(key, temp);
            }
            shiftTemp >>= 1;
        }
        shiftTemp = 128;
        while (shiftTemp > 0){
            if ((cnt[2] & shiftTemp) > 0){
                temp[7] |= shiftTemp;
                NRKGP(key, temp);
            }
            shiftTemp >>= 1;
        }
        return key;
        }
	
	/**
	 * Arregla los bytes de las llaves
	 * @param key
	 * @param ksn
	 * @throws Exception
	 */
	private void NRKGP(byte[] key, byte[] ksn) throws Exception {
		byte[] temp, key_l, key_r, key_temp;
        int i;

        temp = new byte[8];
        key_l = new byte[8];
        key_r = new byte[8];
        key_temp = new byte[8];

        System.arraycopy(key, 0, key_temp, 0, 8);
        for (i = 0; i < 8; i++)
            temp[i] = (byte)(ksn[i] ^ key[8 + i]);
        key_r = DES_Enc(temp, key_temp);
        for (i = 0; i < 8; i++)
            key_r[i] ^= key[8 + i];

        key_temp[0] ^= 0xC0;
        key_temp[1] ^= 0xC0;
        key_temp[2] ^= 0xC0;
        key_temp[3] ^= 0xC0;
        key[8] ^= 0xC0;
        key[9] ^= 0xC0;
        key[10] ^= 0xC0;
        key[11] ^= 0xC0;

        for (i = 0; i < 8; i++)
            temp[i] = (byte)(ksn[i] ^ key[8 + i]);
        key_l = DES_Enc(temp, key_temp);
        for (i = 0; i < 8; i++)
            key[i] = (byte)(key_l[i] ^ key[8 + i]);
        System.arraycopy(key_r, 0, key, 8, 8);
	}
	
    /**
     * Desencripta la informacion
     * @param data
     * @param key
     * @return
     * @throws Exception
     */
    private byte[] DES_Enc(byte[] data, byte[] key) throws Exception {
		byte[] result;
		result = new byte[16];
		Cipher cipher = Cipher.getInstance("DES/ECB/NoPadding",BOUNCY_CASTLE_PROVIDER);
		cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "DES"));
		result = cipher.doFinal(data, 0, data.length);
		return result;
	}
	
 	/**
 	 * Pasa de String a Hex
 	 * @param input
 	 * @return
 	 * @throws Exception
 	 */
 	private byte[] String2Hex(String input) throws Exception {
        byte[] result;
        char[] caTemp;
        int i, len;
        
        caTemp = input.toCharArray();
        result = new byte[caTemp.length];

        len = 0;
        
        for(char tmp : caTemp) {
        	if ((tmp >= '0') && (tmp <= '9'))
                result[len++] = (byte)(tmp - '0');
            else if ((tmp >= 'A') && (tmp <= 'F'))
                result[len++] = (byte)(tmp - 'A' + 10);
            else if ((tmp >= 'a') && (tmp <= 'f'))
                result[len++] = (byte)(tmp - 'a' + 10);
        }
        len >>= 1;
        for (i = 0; i < len; i++)
        	result[i] = (byte)((result[2 * i] << 4) | (result[2 * i + 1]));
        result = Arrays.copyOf(result, len);
		return result;
	}
	
	/**
	 * Genera la llave IPEK
	 * @param ksn
	 * @param DK
	 * @return
	 * @throws Exception
	 */
	private byte[] GenerateIPEK(byte[] ksn, byte [] DK) throws Exception {
		byte[] result;
		byte[] temp, temp2, keyTemp;
		result = new byte[16];
		temp = new byte[8];
		keyTemp = new byte[16];
		temp2 = new byte[8];
		
		System.arraycopy(DK, 0, keyTemp, 0, 16);
		System.arraycopy(ksn, 0, temp, 0, 8);
		temp[7] &= 0xE0;
		
		temp2 = TDES_Enc(temp, keyTemp);
		
		System.arraycopy(temp2, 0, result, 0, 8);
		keyTemp[0] ^= 0xC0;
        keyTemp[1] ^= 0xC0;
        keyTemp[2] ^= 0xC0;
        keyTemp[3] ^= 0xC0;
        keyTemp[8] ^= 0xC0;
        keyTemp[9] ^= 0xC0;
        keyTemp[10] ^= 0xC0;
        keyTemp[11] ^= 0xC0;
        
        temp2 = TDES_Enc(temp,keyTemp);
        System.arraycopy(temp2, 0, result, 8, 8);
		return result;
	}
	
	/**
	 * Encripta la informacion
	 * @param data
	 * @param key
	 * @return
	 * @throws Exception
	 */
	private byte[] TDES_Enc(byte[] data, byte[] key) throws Exception {		
		byte[] result;
		result = new byte[16];
		Cipher cipher = Cipher.getInstance("DESede/ECB/NoPadding",BOUNCY_CASTLE_PROVIDER);
		cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "DESede"));
		result = cipher.doFinal(data, 0, data.length);
		return result;
	}
	
	/**
	 * Desencripta la informacion
	 * @param strData
	 * @param DataKeyVariant
	 * @return
	 * @throws Exception
	 */
	private String TDES_Dec(String strData, String DataKeyVariant) throws Exception {
		String strResult = null;
		byte[] tmp = h2b(DataKeyVariant);
		byte[] key = new byte[24];
		System.arraycopy(tmp, 0, key, 0, 16);
		System.arraycopy(tmp, 0, key, 16, 8);
		Cipher cipher = Cipher.getInstance("DESede/ECB/NoPadding");
		cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "DESede"));
		byte[] plaintext = cipher.doFinal(h2b(strData));
		strResult= b2h(plaintext);
		return strResult;
	}

	/**
	 * Pasa de Hex a byte
	 * @param hex
	 * @return
	 */
	private  byte[] h2b(String hex) {
		if ((hex.length() & 0x01) == 0x01)
			throw new IllegalArgumentException();
		byte[] bytes = new byte[hex.length() / 2];
		for (int idx = 0; idx < bytes.length; ++idx) {
			int hi = Character.digit((int) hex.charAt(idx * 2), 16);
			int lo = Character.digit((int) hex.charAt(idx * 2 + 1), 16);
			if ((hi < 0) || (lo < 0))
				throw new IllegalArgumentException();
			bytes[idx] = (byte) ((hi << 4) | lo);
		}
		return bytes;
	}

	/**
	 * Pasa de Byte a Hex
	 * @param bytes
	 * @return
	 */
	private  String b2h(byte[] bytes) {
		char[] hex = new char[bytes.length * 2];
		for (int idx = 0; idx < bytes.length; ++idx) {
			int hi = (bytes[idx] & 0xF0) >>> 4;
			int lo = (bytes[idx] & 0x0F);
			hex[idx * 2] = (char) (hi < 10 ? '0' + hi : 'A' - 10 + hi);
			hex[idx * 2 + 1] = (char) (lo < 10 ? '0' + lo : 'A' - 10 + lo);
		}
		return new String(hex);
	}

}
